// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MethodTest.cs" company="JDML Consulting Ltd.">
//   Copyright 2011 Photon Project - http://photon.codeplex.com
// </copyright>
// <summary>
//   Defines the MethodTest type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Photon.Testing
{
    using System;
    using System.Linq.Expressions;
    using System.Reflection;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    public class MethodTest
    {
        #region Fields

        private readonly Action _execute;

        #endregion

        public MethodTest(Expression<Action> expression)
        {
            var methodCallExpression = expression.Body as MethodCallExpression;
            if (methodCallExpression == null)
            {
                var newExpression = expression.Body as NewExpression;
                if (newExpression == null)
                {
                    throw new ArgumentException("Expression body must be of type MethodCallExpression or NewExpression");
                }

                Method = newExpression.Constructor;
            }
            else
            {
                Method = methodCallExpression.Method;
            }
            _execute = expression.Compile();
        }

        private MethodBase Method { get; set; }

        private string GetParameterName(int index)
        {
            var parameters = Method.GetParameters();
            if (parameters == null || index < 0 || index >= parameters.Length)
            {
                throw new ArgumentOutOfRangeException("index", index, @"Index out of range.");
            }
            return parameters[index].Name;
        }

        public T AssertThrows<T>() where T : Exception
        {
            return AssertException.Throws<T>(Execute);
        }

        public T AssertThrowsArgumentException<T>(string message, int parameterIndex) where T : ArgumentException
        {
            var exception = AssertThrowsArgumentException<T>(parameterIndex);
            Assert.AreEqual(message, exception.Message);
            return exception;
        }

        public T AssertThrowsArgumentException<T>(int parameterIndex) where T : ArgumentException
        {
            var exception = AssertException.Throws<T>(Execute);
            Assert.AreEqual(GetParameterName(parameterIndex), exception.ParamName);
            return exception;
        }

        public ArgumentException AssertThrowsArgumentException(string message, int parameterIndex)
        {
            return AssertThrowsArgumentException<ArgumentException>(message, parameterIndex);
        }

        public ArgumentException AssertThrowsArgumentException(int parameterIndex)
        {
            return AssertThrowsArgumentException<ArgumentException>(parameterIndex);
        }

        public ArgumentNullException AssertThrowsArgumentNullException(string message, int parameterIndex)
        {
            return AssertThrowsArgumentException<ArgumentNullException>(message, parameterIndex);
        }

        public ArgumentNullException AssertThrowsArgumentNullException(int parameterIndex)
        {
            return AssertThrowsArgumentException<ArgumentNullException>(parameterIndex);
        }

        public ArgumentOutOfRangeException AssertThrowsArgumentOutOrRangeException(string message, object actualValue, int parameterIndex)
        {
            var exception = AssertThrowsArgumentOutOrRangeException(actualValue, parameterIndex);
            Assert.AreEqual(message, exception.Message);
            return exception;
        }

        public ArgumentOutOfRangeException AssertThrowsArgumentOutOrRangeException(object actualValue, int parameterIndex)
        {
            var exception = AssertThrowsArgumentException<ArgumentOutOfRangeException>(parameterIndex);
            Assert.AreEqual(actualValue, exception.ActualValue);
            return exception;
        }

        private void Execute()
        {
            _execute();
        }
    }
}